<?php
class DBmysql extends CRUD
{
    public $dbType = 'read';// read 或 write

	public $_host=''; //数据库所在主机名
    public $_database = '';//当前数据库名
    public $_tablename = '';//当前表的表名
    public $_dt = '';//database.tablename
    public $connectKey = ''; //把已建立的链接存放在$GLOBALS['DB_LINKS']中, 键名为此变量


	public $modelName = ''; //虚拟表名, 对应DBConfig中$TableInfo的键名
    public $isRelease = 0; //查询完成后是否释放
	public $insertId = 0;
	public $affectRows = 0;
	public $custom = FALSE; //是否是直接查询SQL语句, 例如: query('selct * from ....')
	

	public $rs;
	public $data = array(); //查询的主数据
	public $relData = array(); //存放最近一次关联的数据, 暂未使用, 准备为关联多层数据用

	public static $sqls = array();
	public static $currentSql = '';

	//构造函数
    private function __construct($host='', $database='', $tablename='', $isRelease=0)
    {
        $this->_host = $host;//主机名
        $this->_database = $database;//数据库名
        $this->_tablename = $tablename;//表名
        $this->_dt = $database.'.'.$tablename;//数据库名.表名 sql语句中from用了这个值
        $this->isRelease = $isRelease;
    }

	/**
	 * desc 获取链接实例
	 * @param string  $modelName model名
	 * @param int $isRelease 执行完sql语句后是否关闭连接，大并发下需要关闭连接
	 * @return DBmysql|null
     * @throws Exception
	 */
	public static function link($modelName, $isRelease=0)
	{
		$tableinfo 	= DBConfig::getDBInfo($modelName);
		$host = $tableinfo['vhost'];//host vname
		$database = $tableinfo['database'];//database name
		$tablename = $tableinfo['table'];//table name

        return new self($host, $database, $tablename, $isRelease);
	}

	//如果主机没变,并且已经存在MYSQL连接,就不再创建新的连接
	//如果主机改变,就再生成一个实例创建一个连接
    //$type == 'write'或'read'
	public function getConnect($type)
	{
        $this->dbType = $type;

        $this->connectKey = $this->_host.'::'.$this->dbType; //例如 default::read

		//已经存在链接, 直接返回链接名
        if (!empty($GLOBALS['DB_LINKS'][$this->connectKey])) {
        	return $this; // 方便调用事务等其他方法
        }

        //随机选取一个可用的数据库连接(区分读写)
        $hosts = DBConfig::$hosts[$this->_host][$type];

        $isConnected = FALSE;
        $connectCounter = 10; //最多10次重连
        $connectError = array();
        while (!$isConnected && $connectCounter > 0) {
        	$randKey = array_rand($hosts); //随机选取一台mysql主机
	        $config = $hosts[$randKey];
        
        //链接数据库
	        $host = $config['host'];
            $username = $config['username'];
            $password = $config['password'];
	        $port = $config['port'];
	        $charset = $config['charset'];
			
			$mysqli = mysqli_init(); //初始化mysqli
			$mysqli->options(MYSQLI_OPT_CONNECT_TIMEOUT, 3); //超时3s
			$mysqli->options(MYSQLI_INIT_COMMAND, "set names {$charset}");
   
			//连接错误时报错信息会是乱码
            if ($mysqli->real_connect($host, $username, $password, $this->_database, $port)) {
                $GLOBALS['DB_LINKS'][$this->connectKey] = $mysqli;
                $isConnected = TRUE;
            } else {
            	$connectError[$host] = mysqli_connect_error();
            	$isConnected = FALSE;
            	$connectCounter--;
            }
        }

        if (!empty($connectError)) {
        	//有数据库链接失败, sms/email通知相关人员
            //var_dump($connectError);
        }

        if ($isConnected) {
        	return $this;
        } else {
            $this->error('数据库连接失败: '. json_encode($connectError));
        }
        
	}

	/**
	 * 查询封装
	 * @param string $sql
	 * @return $this
     * @throws Exception
	 */
	public function query($sql='')
	{
		if (!empty($sql)) {
		    $this->custom = TRUE;
			self::$sqls[] = self::$currentSql = $sql;
		} else {
            self::$sqls[] = self::$currentSql = $this->sql();
		}
		
		$sql = strtolower(self::$currentSql);
		$sql = ltrim($sql);
		
		if (strlen($sql) == 0) {
		    $this->error('待执行的SQL语句为空');
        }
		
        if (strpos($sql, 'select') === 0) {
            $this->getConnect('read');//读库
        } else {
            $this->getConnect('write');//写库
        }
        
        $connectKey = $this->connectKey;
        
		$this->clearQueryParam(); //清除查询条件
        
		//执行查询语句
		$this->rs = $GLOBALS['DB_LINKS'][$connectKey]->query(self::$currentSql);
		
		($this->rs === FALSE) && $this->error($GLOBALS['DB_LINKS'][$connectKey]->error);

		if (strpos($sql, 'replace') === 0) {
			$this->affectRows = $GLOBALS['DB_LINKS'][$connectKey]->affected_rows;

		} elseif (strpos($sql, 'insert') === 0) {
			$this->insertId = $GLOBALS['DB_LINKS'][$connectKey]->insert_id;

		} elseif (strpos($sql, 'delete') === 0) {
			$this->affectRows = $GLOBALS['DB_LINKS'][$connectKey]->affected_rows;

		} elseif (strpos($sql, 'update') === 0) {
			$this->affectRows = $GLOBALS['DB_LINKS'][$connectKey]->affected_rows;
			
		}

		//查询完成后释放链接, 并删除链接对象
		if ($this->isRelease) {
            $GLOBALS['DB_LINKS'][$connectKey]->close();
			unset($GLOBALS['DB_LINKS'][$connectKey]);
		}

		return $this;
	}

	//一次性获取所有数据到内存
	//如果field不为空，则返回的数组以$field为键重新索引
	public function getAll($field='')
	{
		if (empty($field)) {
			$this->data = $this->rs->fetch_all(MYSQLI_ASSOC); //该函数只能用于php的mysqlnd驱动
			
		} else {
			while ($row = $this->rs->fetch_assoc()) {
				$this->data[$row[$field]] = $row;
			}
		}
		return $this->data;
	}
	
	//获取一条记录
	public function getOne()
	{
		$this->data = $this->rs->fetch_assoc();

		return !empty($this->data) ? $this->data : array();
	}
    
    /**
     * 获取一条记录的某一个字段的值
     * @param $field
     * @return string
     * @throws Exception
     */
	public function getOneValue($field)
	{
        $rs = $this->rs->fetch_assoc();
        
        if (!empty($rs) && !isset($rs[$field])) {
            $this->error('没有发现字段: '.$field);
        }
		return isset($rs[$field]) ? $rs[$field] : '';
	}

	//获取数据集中所有某个字段的值
	public function getValues($field, $index='')
	{
		$this->getAll();
		if (!empty($index)) {
			return array_column($this->data, $field, $index); //以$index字段的值做索引, 以$field字段的值做值
		} else {
			return array_column($this->data, $field);
		}
	}

	//获取总数
	public function getCount()
	{
        $rs = $this->rs->fetch_assoc();
        return isset($rs['SUMMER_N']) ? $rs['SUMMER_N'] : 0;
	}

    //断开数据库连接
    public function close()
    {
        $GLOBALS['DB_LINKS'][$this->connectKey]->close();
    }
    
    //释放数据
    public function freeResult()
    {
        if ($this->rs instanceof mysqli_result) {
            $this->rs->free_result();
        }
    }

    //事务
    //自动提交开关
    public function autoCommit($bool)
    {
        $GLOBALS['DB_LINKS'][$this->connectKey]->autocommit($bool);
        return $this;
    }
    
    //事务开始
    // http://php.net/manual/zh/mysqli.begin-transaction.php
    public function beginTransaction($flag=MYSQLI_TRANS_START_READ_WRITE, $name)
    {
        $GLOBALS['DB_LINKS'][$this->connectKey]->begin_transaction($flag, $name);
        return $this;
    }

    //事务完成提交
    public function commit()
    {
        $GLOBALS['DB_LINKS'][$this->connectKey]->commit();
        return $this;
    }

    //回滚
    public function rollback()
    {
        $GLOBALS['DB_LINKS'][$this->connectKey]->rollback();
        return $this;
    }

	//获取当前连接
	public static function getCurrentLinks()
	{
		return $GLOBALS['DB_LINKS'];
	}
 
	public function error($str)
    {
        // IError::_SetError($str, 'sql');
        throw new Exception($str.'==sql=='. $this->sql());
    }
}